/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pneuservis.soap.client;

import cz.muni.fi.pa165.pneuservis.soap.sei.CustomerDTO;
import cz.muni.fi.pa165.pneuservis.soap.sei.CustomerSEI;
import cz.muni.fi.pa165.pneuservis.soap.sei.Season;
import cz.muni.fi.pa165.pneuservis.soap.sei.ServiceDTO;
import cz.muni.fi.pa165.pneuservis.soap.sei.ServiceSEI;
import cz.muni.fi.pa165.pneuservis.soap.sei.TyreTypeDTO;
import cz.muni.fi.pa165.pneuservis.soap.sei.TyreTypeSEI;
import cz.muni.fi.pa165.pneuservis.soap.sei.impl.CustomerService;
import cz.muni.fi.pa165.pneuservis.soap.sei.impl.ServiceService;
import cz.muni.fi.pa165.pneuservis.soap.sei.impl.TyreTypeService;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.shiro.crypto.hash.Sha256Hash;

/**
 *
 * @author xtarasov
 */
public class Parser {

    private static void printTyre(TyreTypeDTO item) {
        System.out.println(item.getId() + " " + item.getManufacturer()
                + " " + item.getProfile() + "/" + item.getRimSize() + "/"
                + item.getWidth() + " " + item.getSeason() + " " + item.getPrice());
    }

    private static void printService(ServiceDTO item) {
        System.out.println(item.getId() + " " + item.getName() + " " + item.getPrice());
    }

    private static void printCustomer(CustomerDTO item) {
        System.out.println(item.getId() + " " + item.getName() + " " + item.getAddress() + " " + item.getPhone() + " " + item.getUsername());
    }

    private CustomerSEI customerService;
    private ServiceSEI serviceService;
    private TyreTypeSEI tyreTypeService;
    private Iterator<String> tokens;

    public Parser() {

        customerService = (new CustomerService()).getCustomerServicePort();
        serviceService = (new ServiceService()).getServiceServicePort();
        tyreTypeService = (new TyreTypeService()).getTyreTypeServicePort();

    }

    public void run() {
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(System.in, "utf-8"));
            String s;
            print("=======================Welcome============================");
            print("Ready for reciving commands");
            System.out.print(">>>");
            try {
                while ((s = in.readLine()) != null && s.length() != 0) {
                    List<String> commands;
                    if (s.contains("\'") || s.contains("\"")) {
                        commands = parseQuotes(s);
                    } else {
                        commands = Arrays.asList(s.split(" "));
                    }
                    tokens = commands.iterator();

                    try {

                        switch (tokens.next()) {

                            case "create":
                                switch (tokens.next()) {
                                    case "customer":
                                        createCustomer();
                                        break;
                                    case "service":
                                        createService();
                                        break;
                                    case "tyre":
                                        createTyre();
                                        break;
                                    default:
                                        printError("create what?");
                                }
                                break;
                            case "update":
                                switch (tokens.next()) {
                                    case "customer":
                                        updateCustomer();
                                        break;
                                    case "service":
                                        updateService();
                                        break;
                                    case "tyre":
                                        updateTyre();
                                        break;
                                    default:
                                        printError("update what?");
                                }
                                break;
                            case "delete":
                                switch (tokens.next()) {
                                    case "customer":
                                        deleteCustomer();
                                        break;
                                    case "service":
                                        deleteService();
                                        break;
                                    case "tyre":
                                        deleteTyre();
                                        break;
                                    default:
                                        printError("delete what?");
                                }
                                break;
                            case "find":
                                switch (tokens.next()) {
                                    case "customer":
                                        print(findCustomers(), "customers");
                                        break;
                                    case "service":
                                        print(findServices(), "services");
                                        break;
                                    case "tyre":
                                        print(findTyre(), "tyres");
                                        break;
                                    default:
                                        printError("find what?");
                                }
                                break;
                            case "get":
                                switch (tokens.next()) {
                                    case "all":
                                        switch (tokens.next()) {
                                            case "customers":
                                                if (tokens.hasNext()) {
                                                    throw new ParserException("command get all customers has some unexpected tokens");
                                                }
                                                print(customerService.getAllCustomers(), "customers");
                                                break;
                                            case "services":
                                                if (tokens.hasNext()) {
                                                    throw new ParserException("command get all services has some unexpected tokens");
                                                }
                                                print(serviceService.getAllServices(), "services");
                                                break;
                                            case "tyres":
                                                if (tokens.hasNext()) {
                                                    throw new ParserException("command get all tyres has some unexpected tokens");
                                                }
                                                print(tyreTypeService.getAllTyreTypes(), "tyres");
                                                break;
                                            default:
                                                printError("get all what?");
                                        }
                                        break;
                                    default:
                                        printError("get what?");
                                }
                                break;

                            case "end":
                                print("program finished sucessfuly");
                                return;
                            default:
                                printError("command not recoginzed type 'end' for end");
                        }

                    } catch (ParserException e) {
                        printError(e.getMessage());
                    } catch (NoSuchElementException e) {
                        printError("command is too short");
                    }
                    System.out.print(">>>");
                }


            } catch (IOException ex) {
                printError("unexpeced IOException occured, program will terminate" + ex.toString());
            }


        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(Parser.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                in.close();
            } catch (IOException ex) {
                Logger.getLogger(Parser.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

    }

    private void createCustomer() throws ParserException {
        CustomerDTO customer = new CustomerDTO();
        try {
            customer.setName(tokens.next());
            customer.setAddress(tokens.next());
            customer.setPhone(tokens.next());
            customer.setUsername(tokens.next());
            customer.setPassword(tokens.next());

            if (tokens.hasNext()) {
                throw new ParserException("command create customer is too long");
            }

            customerService.createCustomer(customer);

        } catch (NoSuchElementException e) {
            throw new ParserException("command create customer is too short", e);
        } catch (IllegalArgumentException e) {
            throw new ParserException("command create service some arguments are wrong: " + e.getMessage(), e);
        }
        printCustomer(customer);
        print("customer: sucessfuly created");
    }

    private void createService() throws ParserException {
        ServiceDTO service = new ServiceDTO();

        try {
            service.setName(tokens.next());
            double price = NumberFormat.getInstance().parse(tokens.next()).doubleValue();
            service.setPrice(BigDecimal.valueOf(price));
            if (tokens.hasNext()) {
                throw new ParserException("command create service is too long");
            }
            serviceService.createService(service);

        } catch (ParseException ex) {
            throw new ParserException("command create service, price is not a number only", ex);
        } catch (NoSuchElementException e) {
            throw new ParserException("command create service is too short", e);
        } catch (IllegalArgumentException e) {
            throw new ParserException("command create service has some arguments wrong: " + e.getMessage(), e);
        }
        printService(service);
        print("service sucessfuly created");

    }

    private Season parseSeason(String season) throws ParserException {
        switch (season) {
            case "winter":
                return Season.WINTER;
            case "summer":
                return Season.SUMMER;
            case "all seasons":
                return Season.ALL_SEASON;
            default:
                throw new ParserException("Season is incorrect, try: winter, summer or all seasons");
        }


    }

    private void createTyre() throws ParserException {
        TyreTypeDTO tyreTypeTO = new TyreTypeDTO();
        NumberFormat formater = NumberFormat.getInstance();
        try {
            tyreTypeTO.setRimSize(formater.parse(tokens.next()).intValue());
            tyreTypeTO.setWidth(formater.parse(tokens.next()).intValue());
            tyreTypeTO.setProfile(formater.parse(tokens.next()).intValue());
            tyreTypeTO.setSeason(parseSeason(tokens.next()));
            tyreTypeTO.setPrice(BigDecimal.valueOf(formater.parse(tokens.next()).doubleValue()));
            tyreTypeTO.setManufacturer(tokens.next());

            if (tokens.hasNext()) {
                throw new ParserException("command create tyre is too long");
            }
            tyreTypeService.createTyreType(tyreTypeTO);

        } catch (ParseException ex) {
            throw new ParserException("command create tyre, price is not a number only", ex);
        } catch (NoSuchElementException e) {
            throw new ParserException("command create tyre is too short", e);
        } catch (IllegalArgumentException e) {
            throw new ParserException("command create tyre has some arguments wrong: " + e.getMessage(), e);
        }
        printTyre(tyreTypeTO);
        print("tyre: sucessfuly created");
    }

    private void updateCustomer() throws ParserException {
        CustomerDTO customer;
        try {
            if ("-id".equals(tokens.next())) {
                customer = customerService.getCustomer(NumberFormat.getInstance().parse(tokens.next()).longValue());
            } else {
                throw new ParserException("command update customer is missing  -id");
            }
            if (!tokens.hasNext()) {
                throw new ParserException("command update customer is missing parameters for update");
            }
            while (tokens.hasNext()) {
                switch (tokens.next()) {
                    case "-name":
                        customer.setName(tokens.next());
                        break;
                    case "-adress":
                        customer.setAddress(tokens.next());
                        break;
                    case "-phone":
                        customer.setPhone(tokens.next());
                        break;
                    case "-pass":
                        customer.setPassword((new Sha256Hash(tokens.next())).toHex());
                        break;
                    default:
                        throw new ParserException("command update customer has some unexpected parameters ");
                }
            }

            customerService.updateCustomer(customer);


        } catch (ParseException ex) {
            throw new ParserException("command update customer parsing number failed (probably id)", ex);
        } catch (NoSuchElementException e) {
            throw new ParserException("command update customer is too short (some parameters are missing or missplaced)", e);
        } catch (IllegalArgumentException ex) {
            throw new ParserException("command update customer has some wrong parameters:" + ex.getMessage(), ex);
        }
        printCustomer(customer);
        print("customer: sucessfuly updated");
    }

    private void updateService() throws ParserException {
        ServiceDTO service;
        try {
            if ("-id".equals(tokens.next())) {
                service = serviceService.getService(NumberFormat.getInstance().parse(tokens.next()).longValue());
            } else {
                throw new ParserException("command update service is missing  -id");
            }

            if (!tokens.hasNext()) {
                throw new ParserException("command update service is missing parameters for update");
            }

            while (tokens.hasNext()) {
                switch (tokens.next()) {
                    case "-name":
                        service.setName(tokens.next());
                        break;
                    case "-price":
                        service.setPrice(BigDecimal.valueOf(NumberFormat.getInstance().parse(tokens.next()).doubleValue()));
                        break;
                    default:
                        throw new ParserException("command update service has some unexpected parameters");
                }
            }

            serviceService.updateService(service);

        } catch (ParseException ex) {
            throw new ParserException("command update service parsing number failed (probably id or price)", ex);
        } catch (NoSuchElementException e) {
            throw new ParserException("command update service is too short (some parameters are missing)", e);
        } catch (IllegalArgumentException ex) {
            throw new ParserException("command update service has some wrong parameters:" + ex.getMessage(), ex);
        }
        printService(service);
        print("service: sucessfuly updated");
    }

    private void updateTyre() throws ParserException {
        TyreTypeDTO tyreTypeTO;
        NumberFormat formater = NumberFormat.getInstance();
        try {
            if ("-id".equals(tokens.next())) {
                tyreTypeTO = tyreTypeService.getTyreType(NumberFormat.getInstance().parse(tokens.next()).longValue());
            } else {
                throw new ParserException("command update tyre is missing  -id");
            }
            if (!tokens.hasNext()) {
                throw new ParserException("command update tyre is missing parameters for update");
            }

            while (tokens.hasNext()) {
                switch (tokens.next()) {
                    case "-rimSize":
                        tyreTypeTO.setRimSize(formater.parse(tokens.next()).intValue());
                        break;
                    case "-width":
                        tyreTypeTO.setWidth(formater.parse(tokens.next()).intValue());
                        break;
                    case "-profile":
                        tyreTypeTO.setProfile(formater.parse(tokens.next()).intValue());
                        break;
                    case "-season":
                        tyreTypeTO.setSeason(parseSeason(tokens.next()));
                        break;
                    case "-price":
                        tyreTypeTO.setPrice(BigDecimal.valueOf(formater.parse(tokens.next()).doubleValue()));
                        break;
                    case "-manufacturer":
                        tyreTypeTO.setManufacturer(tokens.next());
                    default:
                        throw new ParserException("command update tyre has some unexpected parameters");
                }
            }

            tyreTypeService.updateTyre(tyreTypeTO);
        } catch (ParseException ex) {
            throw new ParserException("command update tyre parsing number failed (probably id or price)", ex);
        } catch (NoSuchElementException e) {
            throw new ParserException("command update tyre is too short (some parameters are missing)", e);
        } catch (IllegalArgumentException ex) {
            throw new ParserException("command update tyre has some wrong parameters:" + ex.getMessage(), ex);
        }
        printTyre(tyreTypeTO);
        print("tyre: sucessfuly updated");
    }

    private void deleteCustomer() throws ParserException {
        CustomerDTO customer;
        try {
            if ("-id".equals(tokens.next())) {
                customer = customerService.getCustomer(NumberFormat.getInstance().parse(tokens.next()).longValue());
                if (tokens.hasNext()) {
                    throw new ParserException("command delete customer is too long");
                }
                customerService.removeCustomer(customer);
            } else {
                throw new ParserException("command update customer is missing  -id");
            }

        } catch (ParseException ex) {
            throw new ParserException("command delete customer, parsing number failed (probably id)", ex);
        } catch (NoSuchElementException e) {
            throw new ParserException("command delete customer is too short (some parameters are missing)", e);
        } catch (IllegalArgumentException ex) {
            throw new ParserException("command delete service has some wrong parameters:" + ex.getMessage(), ex);
        }
        printCustomer(customer);
        print("customer: sucessfuly delted");

    }

    private void deleteService() throws ParserException {
        ServiceDTO service;
        try {
            if ("-id".equals(tokens.next())) {
                service = serviceService.getService(NumberFormat.getInstance().parse(tokens.next()).longValue());
                if (tokens.hasNext()) {
                    throw new ParserException("command delete service is too long");
                }
                serviceService.removeService(service);

            } else {
                throw new ParserException("command update customer is missing  -id");
            }

        } catch (ParseException ex) {
            throw new ParserException("command delete customer, parsing number failed (probably id)", ex);
        } catch (NoSuchElementException e) {
            throw new ParserException("command delete customer is too short (some parameters are missing)", e);
        } catch (IllegalArgumentException ex) {
            throw new ParserException("command delete service has some wrong parameters:" + ex.getMessage(), ex);
        }
        printService(service);
        print("service sucessfuly deleted");
    }

    public void deleteTyre() throws ParserException {
        TyreTypeDTO tyreTypeTO;
        try {
            if ("-id".equals(tokens.next())) {
                tyreTypeTO = tyreTypeService.getTyreType(NumberFormat.getInstance().parse(tokens.next()).longValue());
                if (tokens.hasNext()) {
                    throw new ParserException("command delete tyre is too long");
                }
                tyreTypeService.deleteTyre(tyreTypeTO);

            } else {
                throw new ParserException("command update tyre is missing  -id");
            }

        } catch (ParseException ex) {
            throw new ParserException("command delete tyre, parsing number failed (probably id)", ex);
        } catch (NoSuchElementException e) {
            throw new ParserException("command delete tyre is too short (some parameters are missing)", e);
        } catch (IllegalArgumentException ex) {
            throw new ParserException("command delete tyre has some wrong parameters:" + ex.getMessage(), ex);
        }
        printTyre(tyreTypeTO);
        print("tyre sucessfuly deleted");
    }

    private List<TyreTypeDTO> findTyre() throws ParserException {
        List<TyreTypeDTO> tyres;
        try {
            switch (tokens.next()) {
                case "-profile":
                    tyres = tyreTypeService.findTyreTypesByProfile(NumberFormat.getInstance().parse(tokens.next()).intValue());
                    break;
                case "-manufacturer":
                    tyres = tyreTypeService.findTyreTypesByManufacturer(tokens.next());
                    break;
                case "-rimSize":
                    tyres = tyreTypeService.findTyreTypesByRimSize(NumberFormat.getInstance().parse(tokens.next()).intValue());
                    break;
                case "-width":
                    tyres = tyreTypeService.findTyreTypesByWidth(NumberFormat.getInstance().parse(tokens.next()).intValue());
                    break;
                default:
                    throw new ParserException("command find tyre, has some unexpected parameters");
            }
            if (tokens.hasNext()) {
                throw new ParserException("command find tyre has too much arguments");
            }

        } catch (ParseException ex) {
            throw new ParserException("command find tyre, parsing number failed");
        } catch (NoSuchElementException e) {
            throw new ParserException("command find tyre is too short (some parameters are missing)", e);
        }
        return tyres;

    }

    private List<CustomerDTO> findCustomers() throws ParserException {
        List<CustomerDTO> customers;
        try {
            if ("-name".equals(tokens.next())) {
                customers = customerService.findCustomerByName(tokens.next());
                if (tokens.hasNext()) {
                    throw new ParserException("find customer by name has some unexpected arguments ");
                }
            } else {
                throw new ParserException("command find tyre has unknown option");
            }

        } catch (NoSuchElementException ex) {
            throw new ParserException("command find tyre is too short (some parameters are missing)", ex);
        }
        return customers;
    }

    private List<ServiceDTO> findServices() throws ParserException {
        List<ServiceDTO> services;
        try {
            if ("-name".equals(tokens.next())) {
                services = serviceService.findServiceWithName(tokens.next());
                if (tokens.hasNext()) {
                    throw new ParserException("find services by name has some unexpected arguments ");
                }
            } else {
                throw new ParserException("command find service has unknown option");
            }

        } catch (NoSuchElementException ex) {
            throw new ParserException("command find tyre is too short (some parameters are missing)", ex);
        }
        return services;
    }

    private List<String> parseQuotes(String s) {

        List<String> matchList = new ArrayList<String>();
        Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
        Matcher regexMatcher = regex.matcher(s);
        while (regexMatcher.find()) {
            if (regexMatcher.group(1) != null) {
                // Add double-quoted string without the quotes
                matchList.add(regexMatcher.group(1));
            } else if (regexMatcher.group(2) != null) {
                // Add single-quoted string without the quotes
                matchList.add(regexMatcher.group(2));
            } else {
                // Add unquoted word
                matchList.addAll(Arrays.asList(regexMatcher.group().split(" ")));
            }
        }
        return matchList;
    }

    private static void print(String s) {
        System.out.println(s);
    }

    private static void printError(String s) {
        System.err.println(s);
    }

    private static <T> void print(List<T> items, String itemName) {

        if (items.isEmpty()) {
            System.out.println(itemName + " not found");
            return;
        }
        System.out.println("Found " + itemName + ": ");

        for (T i : items) {
            if (i instanceof CustomerDTO) {
                CustomerDTO item = (CustomerDTO) i;
                printCustomer(item);
            } else if (i instanceof ServiceDTO) {
                ServiceDTO item = (ServiceDTO) i;
                printService(item);
            } else if (i instanceof TyreTypeDTO) {
                TyreTypeDTO item = (TyreTypeDTO) i;
                printTyre(item);
            }
        }

        System.out.println();
    }
}
